"""
$Id: zone.py 2 2007-03-04 19:08:44Z sidnei.da.silva $
"""

import socket
import logging
import threading
from voting import constants

class Zone(threading.Thread):

    def __init__(self, identifier, valid, address):
        threading.Thread.__init__(self)
        self.identifier = identifier
        self.shutdown = False
        self.address = address
        self.lock = threading.Lock()
        self.cond = threading.Condition()
        self.valid = list(valid)
        self.voted = {}
        self.queue = []
        self.logger = logging.getLogger('voting.zone-%s' % self.identifier)
        self.setDaemon(False)

    def can_vote(self, elector):
        if not elector in self.valid:
            return False
        return not self.voted.get(elector, False)

    def register(self, elector, vote):
        self.lock.acquire()
        try:
            # If the elector can't vote, bail out.
            if not self.can_vote(elector):
                return
        finally:
            self.lock.release()

        self.lock.acquire()
        try:
            # Register vote for this elector
            self.voted[elector] = True
            # Remove elector from list of valid for voting
            self.valid.remove(elector)
        finally:
            self.lock.release()

        self.lock.acquire()
        self.cond.acquire()
        try:
            # Append vote to queue
            self.queue.append((elector, vote))
            # Notify waiting threads.
            self.cond.notify()
        finally:
            self.cond.release()
            self.lock.release()

    def run(self):
        try:
            try:
                while True:
                    self.cond.acquire()
                    try:
                        self.cond.wait()
                        self.lock.acquire()
                        try:
                            # While there are votes on the queue, send
                            # them to the central.
                            while self.queue:
                                self.setup()
                                self.send(self.queue.pop())
                                self.teardown()
                            # If everyone has voted, or we've been
                            # asked to shutdown exit the thread.
                            if self.finished():
                                break
                        finally:
                            self.lock.release()
                    finally:
                        self.cond.release()
            except:
                try:
                    self.logger.exception('An exception occurred')
                except:
                    pass
        finally:
            try:
                self.logger.info('Finished')
            except:
                pass

    def setup(self):
        # It's a TCP Socket
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Connect to central
        self.logger.debug('Connecting to central %s', self.address)
        self.sock.connect(self.address)

    def teardown(self):
        self.logger.debug('Disconnecting from %s', self.address)
        # Shutdown the socket.
        self.sock.shutdown(1)

    def stop(self):
        if self.shutdown:
            self.logger.debug('Already finished')
            return
        self.logger.info('Shutdown requested')
        self.cond.acquire()
        try:
            # Notify the thread to shutdown
            self.shutdown = True
            self.cond.notify()
        finally:
            self.cond.release()

    def finished(self):
        if self.shutdown:
            return True
        if not self.valid:
            self.logger.debug('All valid votes received!')
            self.shutdown = True
            return True
        return False

    def send(self, data):
        info = 'zone-%s:%s' % (self.identifier, ':'.join(map(str, data)))
        # First, read what the server replied with
        while True:
            data = self.sock.recv(constants.BUFSIZE)
            if not data:
                break
            self.logger.debug('Server answer: %s', data)
        self.sock.send(info)
